package bancosys.tec.persist.audit.domain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Constraint;
import jmine.tec.persist.annotation.Index;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.Type;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.bussobj.PersistableBusinessObject;
import bancosys.tec.persist.persister.PersistenceAction;
import bancosys.tec.utils.date.Timestamp;

/**
 * @created 03/10/2007
 * @author Gustavo Almeida
 */
@javax.persistence.Entity
@Table(name = "AUDIT_VERSION")
@Alias("AUDVER")
@SequenceGenerator(name = "SEQ_AUDVER", sequenceName = "SEQ_AUDVER")
@Documentation("Armazena as versoes da entidade no processo de auditoria, ou seja, quantas vezes uma entidade foi alterada.")
public class AuditVersion extends PersistableBusinessObject {

    private static final short MAX_LENGTH = 255;

    private Long id;

    private Timestamp ts;

    private String modificationSource;

    private String user;

    private AuditEntity entity;

    private PersistenceAction action;

    private String status;

    private Set<AuditProperty> allProperties;

    /**
     * @return o status em que es encontra a ação
     */
    @Column(name = "STATUS", length = MAX_LENGTH)
    @Documentation("MENSAGEM QUE INDICA O STATUS DA VERSÃO DA ENTIDADE AUDITADA.")
    public String getStatus() {
        return this.status;
    }

    /**
     * @param status em que se encontra a acao
     */
    public void setStatus(String status) {
        this.status = status;
    }

    /**
     * Construtor.
     */
    public AuditVersion() {
        this.allProperties = new TreeSet<AuditProperty>();
        this.ts = new Timestamp();
    }

    /**
     * Adiciona uma property à modificação.
     * 
     * @param property property a ser adicionada.
     */
    public void addProperty(AuditProperty property) {
        this.getAllProperties().add(property);
        property.setVersion(this);
    }

    /**
     * Devolve as properties dessa modificação.
     * 
     * @return as properties dessa modificação.
     * @deprecated usar {@link #getModifiedProperties()}
     */
    @Deprecated
    @Transient
    public Set<AuditProperty> getProperties() {
        return this.getModifiedProperties();
    }

    /**
     * Devolve apenas as properties que tiveram alteracao
     * 
     * @return {@link Set} de {@link AuditProperty}
     */
    @Transient
    public Set<AuditProperty> getModifiedProperties() {
        Set<AuditProperty> copy = new TreeSet<AuditProperty>(this.getAllProperties());
        for (Iterator<AuditProperty> iterator = copy.iterator(); iterator.hasNext();) {
            AuditProperty auditProperty = iterator.next();
            if (this.isModified(auditProperty)) {
                iterator.remove();
            }
        }
        return copy;
    }

    /**
     * Verifica se um {@link AuditProperty} foi modificado
     * 
     * @param auditProperty {@link AuditProperty}
     * @return boolean
     */
    private boolean isModified(AuditProperty auditProperty) {
        if (auditProperty instanceof AuditValuedProperty) {
            AuditValuedProperty value = (AuditValuedProperty) auditProperty;
            if (value.getOldValue() != null && value.getOldValue().equals(value.getNewValue())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Devolve a lista de propriedades formatada para a geração de relatórios.
     * 
     * @return a lista de propriedades formatada.
     */
    @Transient
    public List<AuditValuedProperty> getFormattedProperties() {
        List<AuditValuedProperty> result = new ArrayList<AuditValuedProperty>();
        Set<String> titles = new HashSet<String>();
        for (AuditProperty property : this.getModifiedProperties()) {
            if (property instanceof AuditValuedProperty) {
                String xpath = property.getXpath();

                String titleXpath = "";
                String[] tokens = xpath.split("\\[+\\d\\]\\.");

                for (int i = 0; i < tokens.length - 1; i++) {
                    titleXpath += tokens[i] + ".";
                    if (!titles.contains(titleXpath)) {
                        titles.add(titleXpath);
                        StringBuilder subItemTitle = new StringBuilder();
                        for (int j = 0; j < i; j++) {
                            subItemTitle.append("   ");
                        }
                        subItemTitle.append(tokens[i]);
                        String itemTitle = subItemTitle.toString();
                        AuditValuedProperty newProperty = new AuditValuedProperty();
                        newProperty.setName(itemTitle);
                        newProperty.setXpath(titleXpath);
                        result.add(newProperty);
                    }
                }

                StringBuilder subName = new StringBuilder();
                for (int i = 1; i < tokens.length; i++) {
                    subName.append("   ");
                }
                if (property.getName() != null) {
                    subName.append(property.getName());
                } else {
                    subName.append(tokens[tokens.length - 1]);
                }

                String name = subName.toString();
                property.setName(name);
                result.add((AuditValuedProperty) property);
            }
        }

        Collections.sort(result, new Comparator<AuditValuedProperty>() {
            public int compare(AuditValuedProperty o1, AuditValuedProperty o2) {
                return o1.compareTo(o2);
            }
        });

        return result;
    }

    /**
     * Devolve o id de persistência.
     * 
     * @return o id de persistência.
     */
    @Id
    @Column(name = "COD_AUDIT_VERSION")
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_AUDVER")
    @Documentation("CODIGO DA VERSÃO DA ENTIDADE AUDITADA.")
    public Long getId() {
        return this.id;
    }

    /**
     * Define o id.
     * 
     * @param id O id a ser definido.
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Devolve o timestamp da modificação.
     * 
     * @return o timestamp da modificação.
     */
    @Type(type = "bancosys.tec.persist.hibernate.type.TimestampType")
    @Column(name = "TIMESTAMP")
    @Documentation("DATA/HORA DA ALTERAÇÃO DA ENTIDADE.")
    public Timestamp getTs() {
        return this.ts;
    }

    /**
     * Define o timestamp da modificação.
     * 
     * @param ts o timestamp da modificação.
     */
    public void setTs(Timestamp ts) {
        this.ts = ts;
    }

    /**
     * Devolve a origem da modificação.
     * 
     * @return a origem da modificação.
     */
    @Column(name = "SOURCE", length = MAX_LENGTH)
    @Documentation("ORIGEM DA ALTERAÇÃO QUE GEROU A AUDITORIA.")
    public String getModificationSource() {
        return this.modificationSource;
    }

    /**
     * Define o modificationSource.
     * 
     * @param modificationSource O modificationSource a ser definido.
     */
    public void setModificationSource(String modificationSource) {
        this.modificationSource = modificationSource;
    }

    /**
     * Devolve o usuário que originou a modificação.
     * 
     * @return o usuário que originou a modificação.
     */
    @Column(name = "AUTHOR", length = MAX_LENGTH)
    @Documentation("USUÁRIO AUTOR DA ALTERAÇÃO.")
    public String getUser() {
        return this.user;
    }

    /**
     * Define o usuário que originou a modificação.
     * 
     * @param user o usuário que originou a modificação.
     */
    public void setUser(String user) {
        this.user = user;
    }

    /**
     * Devolve a ação executada.
     * 
     * @return a ação executada.
     */
    @Column(name = "ACTION")
    @Documentation("AÇÃO QUE GEROU A AUDITORIA. 0. INCLUSÃO; 1. ALTERAÇÃO; 2. EXCLUSÃO; 3. INCLUSÃO NA BASE FRIA; "
            + " 4. ALTERAÇÃO NA BASE FRIA; 5. EXCLUSÃO DA BASE FRIA; 6. INCLUSÃO COM AÇÃO; 7. ALTERAÇÃO COM AÇÃO; 8. REJEIÇÃO DA AUTORIZAÇÃO.")
    @Constraint(suffix = "0")
    public PersistenceAction getAction() {
        return this.action;
    }

    /**
     * Define a ação executada.
     * 
     * @param action a ação executada.
     */
    public void setAction(PersistenceAction action) {
        this.action = action;
    }

    /**
     * Devolve a entidade.
     * 
     * @return a entidade.
     */
    @ManyToOne
    @ForeignKey(name = "FK_AUDENT_AUDVER")
    @JoinColumn(name = "COD_AUDIT_ENTITY")
    @Documentation("CODIGO DA ENTIDADE DO PROCESSO DE AUDITORIA.")
    @Index(suffix = "0")
    public AuditEntity getEntity() {
        return this.entity;
    }

    /**
     * Define o entity.
     * 
     * @param entity entidade.
     */
    public void setEntity(AuditEntity entity) {
        this.entity = entity;
    }

    /**
     * @return the allProperties
     */
    // (lundberg) SEM DELETE ORPHANS, MUITO INEFICIENTE
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "version")
    @Cascade({ CascadeType.ALL })
    public Set<AuditProperty> getAllProperties() {
        return this.allProperties;
    }

    /**
     * @param allProperties the allProperties to set
     */
    public void setAllProperties(Set<AuditProperty> allProperties) {
        this.allProperties = allProperties;
    }

    /**
     * Remove todas as propriedades
     */
    public void clearAllProperties() {
        this.getAllProperties().clear();
    }

    /**
     * Adiciona todas as propriedades a colecao
     * 
     * @param props {@link Collection} de {@link AuditProperty}
     */
    public void addAllProperties(Collection<? extends AuditProperty> props) {
        for (AuditProperty auditProperty : props) {
            this.addProperty(auditProperty);
        }
    }

    /**
     * Devolve as propriedades que nao foram modificadas
     * 
     * @return {@link Set}
     */
    @Transient
    public Set<? extends AuditProperty> getUnmodifiedProperties() {
        Set<AuditProperty> set = new TreeSet<AuditProperty>();
        for (AuditProperty auditProperty : this.getAllProperties()) {
            if (!this.isModified(auditProperty)) {
                set.add(auditProperty);
            }
        }
        return set;
    }

}
